package com.example.study.config.shiro;

import com.example.study.plus.mybatis_plus.entity.TokenUser;
import com.example.study.plus.mybatis_plus.entity.User;
import com.example.study.plus.mybatis_plus.service.TokenUserService;
import com.example.study.plus.mybatis_plus.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;

@Slf4j
public class CustomRealm extends AuthorizingRealm {


    @Autowired
    @Lazy
    UserService userService;
    @Autowired
    @Lazy
    TokenUserService tokenUserService;

    /**
     * Shiro 的权限授权是通过继承AuthorizingRealm抽象类，重载doGetAuthorizationInfo();
     * 当访问到页面的时候，链接配置了相应的权限或者 Shiro 标签才会执行此方法否则不会执行，
     * 所以如果只是简单的身份认证没有权限的控制的话，那么这个方法可以不进行实现，直接返回 null 即可。
     *
     * @param principalCollection x
     * @return null
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        return null;
    }

    /**
     * 在认证、授权内部实现机制中都有提到，最终处理都将交给Real进行处理。
     * 因为在 Shiro 中，最终是通过 Realm 来获取应用程序中的用户、角色及权限信息的。
     * 通常情况下，在 Realm 中会直接从我们的数据源中获取 Shiro 需要的验证信息。
     * 可以说，Realm 是专用于安全框架的 DAO. Shiro 的认证过程最终会交由 Realm 执行，
     * 这时会调用 Realm 的getAuthenticationInfo(token)方法。
     * <p>
     * 该方法主要执行以下操作:
     * <p>
     * 1、检查提交的进行认证的令牌信息
     * 2、根据令牌信息从数据源(通常为数据库)中获取用户信息
     * 3、对用户信息进行匹配验证。
     * 4、验证通过将返回一个封装了用户信息的AuthenticationInfo实例。
     * 5、验证失败则抛出AuthenticationException异常信息。
     *
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {

// //获取token，既前端传入的token
//        String accessToken = (String) token.getPrincipal();
//        //1. 根据accessToken，查询用户信息
//        SysToken tokenEntity = shiroService.findByToken(accessToken);
//        //2. token失效
//        if (tokenEntity == null || tokenEntity.getExpireTime().getTime() < System.currentTimeMillis()) {
//            throw new IncorrectCredentialsException("token失效，请重新登录");
//        }
//        //3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
//        User user = shiroService.findByUserId(tokenEntity.getUserId());
//        //4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
//        if (user == null) {
//            throw new UnknownAccountException("用户不存在!");
//        }
//        //5. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
//        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(user, accessToken, this.getName());
//        return info;


        String token = (String) authenticationToken.getPrincipal();
        // 解密获得username，用于和数据库进行对比
        String username = null;
        try {
            //这里工具类没有处理空指针等异常这里处理一下(这里处理科学一些)
            username = JwtUtil.getUsername(token);
        } catch (Exception e) {
            throw new AuthenticationException("heard的token拼写错误或者值为空");
        }
        if (username == null) {
            System.out.println("token无效(空''或者null都不行!)");
            throw new AuthenticationException("token无效");
        }
        TokenUser token1 = tokenUserService.findTokenByUserName(username);
        if (token1 == null) {
            log.error("当前登录已被删除");
            throw new AuthenticationException("当前登录已被删除");
        }
        if (!token1.getToken().equals(token)) {
            log.error("其他端正在登录");
            throw new AuthenticationException("其他端正在登录");
        }

        User user = userService.getUserByUsername(username);
        if (user == null) {
            System.out.println("用户不存在!)");
            throw new AuthenticationException("用户不存在!");
        }
        log.info("token: " + token + "\nusername: " + username + "\nPassword: " + user.getPassword());
        if (!JwtUtil.verify(token, username, user.getPassword())) {
            log.error("用户名或密码错误(token无效或者与登录者不匹配)!");
            throw new AuthenticationException("用户名或密码错误(token无效或者与登录者不匹配)!");
        }
        return new SimpleAuthenticationInfo(token, token, this.getName());
    }
}
